001 /*
002 * Copyright 2006 Stephen J. McConnell.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
013 * implied.
014 *
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018
019 package net.dpml.util;
020
021 import java.util.EventObject;
022 import java.util.List;
023 import java.util.LinkedList;
024
025 import net.dpml.transit.monitor.LoggingAdapter;
026
027 /**
028 * A abstract base class that established an event queue and handles event dispatch
029 * operations for listeners declared in classes extending this base class.
030 *
031 * @author <a href="http://www.dpml.net">The Digital Product Meta Library</a>
032 * @version 1.0.0
033 */
034 public class EventQueue
035 {
036 // ------------------------------------------------------------------------
037 // state
038 // ------------------------------------------------------------------------
039
040 private final EventDispatchThread m_thread;
041
042 private final Logger m_logger;
043
044 private final List m_queue;
045
046 // ------------------------------------------------------------------------
047 // constructor
048 // ------------------------------------------------------------------------
049
050 /**
051 * Creation of a new event queue.
052 * @param name the name used to construct a logging channel
053 */
054 public EventQueue( String name )
055 {
056 this( getLoggerForCategory( name ) );
057 }
058
059 /**
060 * Creation of a new model.
061 * @param logger the assigned logging channel
062 * @exception NullPointerException if the supplied logging channel is null
063 */
064 public EventQueue( Logger logger )
065 throws NullPointerException
066 {
067 if( null == logger )
068 {
069 throw new NullPointerException( "logger" );
070 }
071 m_logger = logger;
072 m_queue = new LinkedList();
073 m_thread = new EventDispatchThread();
074 m_thread.setDaemon( true );
075 m_thread.start();
076 }
077
078 // ------------------------------------------------------------------------
079 // EventQueue
080 // ------------------------------------------------------------------------
081
082 /**
083 * Terminate the dispatch thread.
084 */
085 public synchronized void terminateDispatchThread()
086 {
087 if( null != m_thread )
088 {
089 m_thread.dispose();
090 }
091 }
092
093 /**
094 * Return the assigned logging channel.
095 * @return the logging channel
096 */
097 private Logger getLogger()
098 {
099 return m_logger;
100 }
101
102 /**
103 * A single background thread ("the event notification thread") monitors
104 * the event queue and delivers events that are placed on the queue.
105 */
106 private class EventDispatchThread extends Thread
107 {
108 private boolean m_continue = true;
109
110 void dispose()
111 {
112 synchronized( m_queue )
113 {
114 m_continue = false;
115 m_queue.notify();
116 }
117 }
118
119 public void run()
120 {
121 while( m_continue )
122 {
123 // Wait on m_queue till an event is present
124 EventObject event = null;
125 synchronized( m_queue )
126 {
127 try
128 {
129 while( m_continue && m_queue.isEmpty() )
130 {
131 m_queue.wait();
132 }
133 if ( !m_continue )
134 {
135 break;
136 }
137 Object object = m_queue.remove( 0 );
138 try
139 {
140 event = (EventObject) object;
141 }
142 catch( ClassCastException cce )
143 {
144 final String error =
145 "Unexpected class cast exception while processing an event."
146 + "\nEvent: " + object;
147 throw new IllegalStateException( error );
148 }
149 }
150 catch( InterruptedException e )
151 {
152 return;
153 }
154 }
155
156 Object source = event.getSource();
157 if( source instanceof EventHandler )
158 {
159 EventHandler handler = (EventHandler) source;
160 try
161 {
162 handler.processEvent( event );
163 }
164 catch( Throwable e )
165 {
166 final String error =
167 "Unexpected error while processing event."
168 + "\nEvent: " + event
169 + "\nSource: " + this;
170 getLogger().error( error, e );
171 }
172 }
173 else
174 {
175 final String error =
176 "Event source is not an instance of "
177 + EventHandler.class.getName();
178 getLogger().error( error );
179 }
180 }
181 }
182 }
183
184 /**
185 * Enqueue an event for delivery to registered
186 * listeners unless there are no registered
187 * listeners.
188 *
189 * @param event the event object to add to the queue
190 */
191 public void enqueueEvent( EventObject event )
192 {
193 enqueueEvent( event, false );
194 }
195
196 /**
197 * Enqueue an event for delivery to registered
198 * listeners unless there are no registered
199 * listeners.
200 *
201 * @param event the event object to add to the queue
202 * @param waitForCompletion if TRUE the implementation will apply
203 * the event to the event source event handler and return on
204 * copmpletion of evetn delivery
205 */
206 public void enqueueEvent( EventObject event, boolean waitForCompletion )
207 {
208 if( !waitForCompletion )
209 {
210 synchronized( m_queue )
211 {
212 m_queue.add( event );
213 m_queue.notify();
214 }
215 }
216 else
217 {
218 Object source = event.getSource();
219 if( source instanceof EventHandler )
220 {
221 EventHandler handler = (EventHandler) source;
222 try
223 {
224 handler.processEvent( event );
225 }
226 catch( Throwable e )
227 {
228 final String error =
229 "Unexpected error while processing event."
230 + "\nEvent: " + event
231 + "\nSource: " + source;
232 getLogger().error( error );
233 }
234 }
235 else
236 {
237 final String error =
238 "Event source is not an instance of "
239 + EventHandler.class.getName()
240 + "\nSource: " + source.getClass().getName();
241 throw new IllegalStateException( error );
242 }
243 }
244 }
245
246 /**
247 * Return a logging channel for the supplied name.
248 * @param name the name to use in construction of the logging channel
249 * @return the logging channel
250 */
251 static Logger getLoggerForCategory( String name )
252 {
253 if( null == name )
254 {
255 return new LoggingAdapter( "" );
256 }
257 else
258 {
259 return new LoggingAdapter( name );
260 }
261 }
262 }